using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using IppServer.Host.Configuration;

namespace IppServer.Host.TcpRaw
{
    internal sealed class Tcp9100PrintServer : IDisposable
    {
        private const int BufferSize = 8192;
        private const int ClientTimeoutMilliseconds = 5 * 60 * 1000;
        private const int MaxConcurrentClients = 10;
        private const int PrinterDriverXps = 0x00000008;
        private const int ErrorInsufficientBuffer = 122;

        private readonly Action<string> _log;
        private Func<int, string, string, string, long, int> _recordJobCallback; // 记录作业回调：返回 jobId
        private Action<int, int, string> _updateJobStateCallback; // 更新作业状态回调
        private readonly SemaphoreSlim _clientSemaphore = new SemaphoreSlim(MaxConcurrentClients);
        private readonly List<TcpListener> _listeners = new List<TcpListener>();
        private readonly List<Task> _listenerTasks = new List<Task>();
        private readonly Dictionary<int, string> _portToPrinter = new Dictionary<int, string>();
        private readonly Dictionary<int, string> _portToPrintMode = new Dictionary<int, string>();
        private readonly Dictionary<int, CancellationTokenSource> _listenerTokens = new Dictionary<int, CancellationTokenSource>();
        private readonly object _stateLock = new object();

        private bool _isRunning;

        public Tcp9100PrintServer(Action<string> logCallback)
        {
            _log = logCallback ?? (_ => { });
        }
        
        /// <summary>
        /// 设置作业记录回调（由 IppServerHost 通过 MainForm 设置）
        /// </summary>
        public void SetJobCallbacks(
            Func<int, string, string, string, long, int> recordJob,
            Action<int, int, string> updateJobState)
        {
            _recordJobCallback = recordJob;
            _updateJobStateCallback = updateJobState;
        }

        public bool IsRunning
        {
            get
            {
                lock (_stateLock)
                {
                    return _isRunning;
                }
            }
        }

        public IReadOnlyList<TcpPrinterTask> CurrentTasks { get; private set; } = Array.Empty<TcpPrinterTask>();

        public void Start(IEnumerable<TcpPrinterTask> tasks)
        {
            if (tasks == null)
            {
                throw new ArgumentNullException(nameof(tasks));
            }

            lock (_stateLock)
            {
                if (_isRunning)
                {
                    throw new InvalidOperationException("TCP 9100 服务已经在运行。");
                }

                var taskList = tasks
                    .Where(t => !string.IsNullOrWhiteSpace(t.PrinterName))
                    .Select(t => new TcpPrinterTask
                    {
                        PrinterName = t.PrinterName.Trim(),
                        Port = t.Port <= 0 ? 9100 : t.Port,
                        PrintMode = string.IsNullOrWhiteSpace(t.PrintMode) ? "自动" : t.PrintMode.Trim()
                    })
                    .ToList();

                if (taskList.Count == 0)
                {
                    throw new InvalidOperationException("没有可用的 TCP 打印任务。");
                }

                if (taskList.GroupBy(t => t.Port).Any(g => g.Count() > 1))
                {
                    throw new InvalidOperationException("存在端口重复的 TCP 打印任务。");
                }

                try
                {
                    foreach (var task in taskList)
                    {
                        StartListener(task);
                    }
                }
                catch
                {
                    StopAsync().GetAwaiter().GetResult();
                    throw;
                }

                CurrentTasks = taskList;
                _isRunning = true;
                _log("TCP 9100 打印服务已启动。");
            }
        }

        public async Task StopAsync()
        {
            List<Task> runningTasks;
            var hadListeners = false;
            lock (_stateLock)
            {
                if (_listeners.Count == 0 && _listenerTokens.Count == 0)
                {
                    _isRunning = false;
                    return;
                }

                hadListeners = true;
                foreach (var kvp in _listenerTokens.ToList())
                {
                    kvp.Value.Cancel();
                }

                foreach (var listener in _listeners)
                {
                    try
                    {
                        listener.Stop();
                    }
                    catch
                    {
                        // 忽略
                    }
                }

                runningTasks = _listenerTasks.ToList();

                _listeners.Clear();
                _listenerTasks.Clear();
                _listenerTokens.Clear();
                _portToPrinter.Clear();
                _portToPrintMode.Clear();
                CurrentTasks = Array.Empty<TcpPrinterTask>();
                _isRunning = false;
            }

            if (runningTasks != null)
            {
                foreach (var task in runningTasks)
                {
                    try
                    {
                        await task.ConfigureAwait(false);
                    }
                    catch
                    {
                        // 忽略
                    }
                }
            }

            if (hadListeners)
            {
                _log("TCP 9100 打印服务已停止。");
            }
        }

        public void Dispose()
        {
            StopAsync().GetAwaiter().GetResult();
            _clientSemaphore.Dispose();
        }

        private void StartListener(TcpPrinterTask task)
        {
            var printerName = task.PrinterName;
            var port = task.Port;
            var printMode = task.PrintMode;

            var cts = new CancellationTokenSource();
            var listener = new TcpListener(IPAddress.Any, port);

            listener.Start();
            _listeners.Add(listener);
            _listenerTokens[port] = cts;
            _portToPrinter[port] = printerName;
            _portToPrintMode[port] = printMode;

            _log($"TCP 服务监听端口 {port} -> 打印机 [{printerName}] (模式: {printMode})");

            var taskRunner = Task.Run(() => ServerLoopAsync(listener, port, cts.Token), cts.Token);
            _listenerTasks.Add(taskRunner);
        }

        private async Task ServerLoopAsync(TcpListener listener, int port, CancellationToken cancellationToken)
        {
            try
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    if (!listener.Pending())
                    {
                        await Task.Delay(100, cancellationToken).ConfigureAwait(false);
                        continue;
                    }

                    var client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
                    client.ReceiveTimeout = ClientTimeoutMilliseconds;
                    client.SendTimeout = ClientTimeoutMilliseconds;

                    var printerName = _portToPrinter.TryGetValue(port, out var pn) ? pn : string.Empty;
                    var printMode = _portToPrintMode.TryGetValue(port, out var pm) ? pm : "自动";
                    var clientIp = ((IPEndPoint)client.Client.RemoteEndPoint)?.Address?.ToString() ?? "未知";

                    // IP/MAC地址限制验证
                    try
                    {
                        var settings = Configuration.AppSettingsStore.Load();
                        var restriction = settings?.IpMacRestriction;
                        if (restriction != null && restriction.Enabled)
                        {
                            var clientMac = Configuration.IpMacValidator.GetMacAddressFromIp(clientIp);
                            if (!Configuration.IpMacValidator.IsAllowed(restriction, clientIp, clientMac, out var denyReason))
                            {
                                _log($"[TCP {port}] 客户端 {clientIp} 访问被拒绝：{denyReason}");
                                client.Close();
                                continue;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _log($"[TCP {port}] 验证IP/MAC限制时发生错误：{ex.Message}，允许连接继续");
                    }

                    _log($"接收到 TCP 客户端，源: {clientIp}，端口 {port} -> 打印机 [{printerName}] 模式 {printMode}");

                    await _clientSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

                    _ = Task.Run(async () =>
                    {
                        try
                        {
                            await HandleClientAsync(client, printerName, printMode, port, clientIp, cancellationToken).ConfigureAwait(false);
                        }
                        catch (Exception ex)
                        {
                            _log($"处理 TCP 客户端失败：{ex.Message}");
                        }
                        finally
                        {
                            _clientSemaphore.Release();
                        }
                    }, cancellationToken);
                }
            }
            catch (OperationCanceledException)
            {
                // 忽略
            }
            catch (Exception ex)
            {
                _log($"端口 {port} 监听失败：{ex.Message}");
            }
        }

        private async Task HandleClientAsync(TcpClient client, string printerName, string printMode, int port, string clientIp, CancellationToken cancellationToken)
        {
            int jobId = 0;
            using (client)
            using (var networkStream = client.GetStream())
            using (var ms = new MemoryStream())
            {
                var buffer = new byte[BufferSize];
                while (true)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    int bytesRead;
                    try
                    {
                        bytesRead = await networkStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                    }
                    catch (IOException ioEx)
                    {
                        _log($"读取客户端数据失败: {ioEx.Message}");
                        if (jobId > 0)
                        {
                            _updateJobStateCallback?.Invoke(jobId, 8, "读取数据失败：" + ioEx.Message); // cancelled
                        }
                        return;
                    }

                    if (bytesRead <= 0)
                    {
                        break;
                    }

                    ms.Write(buffer, 0, bytesRead);
                }

                if (ms.Length == 0)
                {
                    _log("收到空的打印任务，已忽略。");
                    return;
                }

                if (!IsPrinterAvailable(printerName))
                {
                    _log($"打印机 [{printerName}] 不可用，任务被忽略。");
                    return;
                }

                // 记录作业
                var dataSize = ms.Length;
                if (_recordJobCallback != null)
                {
                    jobId = _recordJobCallback(port, printerName, clientIp, printMode, dataSize);
                }

                try
                {
                    await Task.Run(() => PrintBytesToPrinter(ms.ToArray(), printerName, printMode, jobId), cancellationToken).ConfigureAwait(false);
                    // 更新作业状态为完成
                    if (jobId > 0)
                    {
                        _updateJobStateCallback?.Invoke(jobId, 9, null); // completed
                    }
                }
                catch (Exception ex)
                {
                    _log($"打印失败：{ex.Message}");
                    // 更新作业状态为错误
                    if (jobId > 0)
                    {
                        _updateJobStateCallback?.Invoke(jobId, 8, ex.Message); // error (使用 cancelled 状态码，但通过 errorMessage 区分)
                    }
                    throw;
                }
            }
        }

        private static bool IsPrinterAvailable(string printerName)
        {
            try
            {
                var printer = new PrinterSettings
                {
                    PrinterName = printerName
                };
                return printer.IsValid;
            }
            catch
            {
                return false;
            }
        }

        private void PrintBytesToPrinter(byte[] data, string printerName, string printMode, int jobId = 0)
        {
            if (data == null || data.Length == 0)
            {
                _log("没有可打印的数据。");
                if (jobId > 0)
                {
                    _updateJobStateCallback?.Invoke(jobId, 8, "没有可打印的数据");
                }
                return;
            }

            var dataType = GetPrinterDataType(printerName, printMode);
            var docName = jobId > 0 ? $"TCP9100 Job #{jobId}" : $"TCP9100 Job {DateTime.Now:yyyyMMddHHmmss}";
            _log($"发送打印任务到 [{printerName}]，长度 {data.Length} 字节。模式: {printMode} -> 数据类型: {dataType}");

            // 更新作业状态为打印中
            if (jobId > 0)
            {
                _updateJobStateCallback?.Invoke(jobId, 5, null); // processing
            }

            if (!SendRawDataToPrinter(data, printerName, docName, dataType))
            {
                var errorMsg = $"打印机 [{printerName}] 无法完成任务。";
                _log(errorMsg);
                if (jobId > 0)
                {
                    _updateJobStateCallback?.Invoke(jobId, 8, errorMsg);
                }
            }
        }

        private string GetPrinterDataType(string printerName, string printMode)
        {
            if (string.Equals(printMode, "XPS", StringComparison.OrdinalIgnoreCase))
            {
                return "XPS_PASS";
            }

            if (string.Equals(printMode, "RAW", StringComparison.OrdinalIgnoreCase))
            {
                return "RAW";
            }

            IntPtr hPrinter = IntPtr.Zero;
            try
            {
                if (!OpenPrinter(printerName, out hPrinter, IntPtr.Zero))
                {
                    return "RAW";
                }

                        int bufferSize = 0;
                if (!GetPrinterDriver(hPrinter, null, 8, IntPtr.Zero, 0, ref bufferSize))
                {
                    var error = Marshal.GetLastWin32Error();
                    if (error != ErrorInsufficientBuffer || bufferSize <= 0)
                    {
                        return "RAW";
                    }
                }

                IntPtr driverInfo = Marshal.AllocHGlobal(bufferSize);
                try
                {
                    if (!GetPrinterDriver(hPrinter, null, 8, driverInfo, bufferSize, ref bufferSize))
                    {
                        return "RAW";
                    }

                    uint attributes = (uint)Marshal.ReadInt32(driverInfo, 8);
                    var isXps = (attributes & PrinterDriverXps) != 0;
                    return isXps ? "XPS_PASS" : "RAW";
                }
                finally
                {
                    Marshal.FreeHGlobal(driverInfo);
                }
            }
            catch
            {
                return "RAW";
            }
            finally
            {
                if (hPrinter != IntPtr.Zero)
                {
                    ClosePrinter(hPrinter);
                }
            }
        }

        private bool SendRawDataToPrinter(byte[] data, string printerName, string docName, string dataType)
        {
            if (data == null || data.Length == 0)
            {
                return false;
            }

            IntPtr hPrinter = IntPtr.Zero;
            var docInfo = new RawPrinterHelper.DOCINFO
            {
                pDocName = docName,
                pDataType = dataType
            };

            try
            {
                if (!RawPrinterHelper.OpenPrinter(printerName.Normalize(), ref hPrinter, IntPtr.Zero))
                {
                    var error = Marshal.GetLastWin32Error();
                    _log($"打开打印机 [{printerName}] 失败：{error}");
                    return false;
                }

                if (!RawPrinterHelper.StartDocPrinter(hPrinter, 1, docInfo))
                {
                    var error = Marshal.GetLastWin32Error();
                    _log($"开始打印 [{printerName}] 失败：{error}");
                    return false;
                }

                const int chunkSize = 32 * 1024;
                var buffer = new byte[chunkSize];
                var offset = 0;

                while (offset < data.Length)
                {
                    var toCopy = Math.Min(chunkSize, data.Length - offset);
                    Array.Copy(data, offset, buffer, 0, toCopy);

                    if (!RawPrinterHelper.WritePrinter(hPrinter, buffer, toCopy, out var written) || written <= 0)
                    {
                        var error = Marshal.GetLastWin32Error();
                        _log($"向 [{printerName}] 写入数据失败：{error}");
                        return false;
                    }

                    offset += written;
                }

                return true;
            }
            catch (Exception ex)
            {
                _log($"向打印机 [{printerName}] 发送数据失败：{ex.Message}");
                return false;
            }
            finally
            {
                if (hPrinter != IntPtr.Zero)
                {
                    RawPrinterHelper.EndDocPrinter(hPrinter);
                    RawPrinterHelper.ClosePrinter(hPrinter);
                }
            }
        }

        private static class RawPrinterHelper
        {
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
            public class DOCINFO
            {
                [MarshalAs(UnmanagedType.LPStr)] public string pDocName;
                [MarshalAs(UnmanagedType.LPStr)] public string pOutputFile;
                [MarshalAs(UnmanagedType.LPStr)] public string pDataType;
            }

            [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, ref IntPtr hPrinter, IntPtr pd);

            [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool ClosePrinter(IntPtr hPrinter);

            [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool StartDocPrinter(IntPtr hPrinter, int level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFO di);

            [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool EndDocPrinter(IntPtr hPrinter);

            [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool WritePrinter(IntPtr hPrinter, byte[] pBytes, int dwCount, out int dwWritten);
        }

        [DllImport("winspool.drv", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool GetPrinterDriver(IntPtr hPrinter, string pEnvironment, int level, IntPtr pDriverInfo, int cbBuf, ref int pcbNeeded);

        [DllImport("winspool.drv", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);

        [DllImport("winspool.drv", SetLastError = true)]
        private static extern bool ClosePrinter(IntPtr hPrinter);
    }
}

